package excel

import (
	"bytes"
	"fmt"
	"io"
	"reflect"

	"github.com/xuri/excelize/v2"
)

const (
	DefaultSheet = "Sheet1"
)

var (
	ObjectError = fmt.Errorf("object is not Struct")
	SliceError  = fmt.Errorf("object is not Slice")
)

type IXlsx interface {
	Read(reader io.Reader) ISheet
	StreamWrite() IXlsx
	Template(obj interface{}) (*bytes.Buffer, error)
	WriteToBuffer() (*bytes.Buffer, error)
	SaveAs(path string) error
	NewSheet(sheet string) ISheet
	WriteTo(w io.Writer) (int64, error)
}

type ISheet interface {
	InsertRows(array []interface{}) ISheet
	InsertIndexRows(row int, array []interface{}) ISheet
	SetHeader(obj interface{}) ISheet
	Error() error
	//Scan(sheet string, result interface{}) ISheet
}

type sheetTable struct {
	sheet        string
	file         *excelize.File
	streamWriter *excelize.StreamWriter
	err          error
	row          int
}

func (s *sheetTable) Error() error {
	return s.err
}

func (s *sheetTable) InsertIndexRows(row int, array []interface{}) ISheet {
	if s.err != nil {
		return s
	}
	s.row = row
	return s.InsertRows(array)
}

func (s *sheetTable) InsertRows(array []interface{}) ISheet {
	if s.err != nil {
		return s
	}
	for _, v := range array {
		lineInfo, err := objectValue(v)
		if err != nil {
			s.err = err
			return s
		}
		cell, _ := excelize.CoordinatesToCellName(1, s.row)

		s.err = s.streamWriter.SetRow(cell, lineInfo)
		s.row += 1
	}
	err := s.streamWriter.Flush()
	if err != nil {
		s.err = err
		return s
	}
	return s
}

func (s *sheetTable) SetHeader(obj interface{}) ISheet {
	if s.err != nil {
		return s
	}

	headers, err := objectHeader(obj)
	if err != nil {
		s.err = err
		return s
	}
	cell, _ := excelize.CoordinatesToCellName(1, 1)

	s.err = s.streamWriter.SetRow(cell, headers)

	return s
}

//func (s *sheetTable) Scan(sheet string, result interface{}) ISheet {
//	if s.err != nil {
//		return s
//	}
//
//	rows, err := s.file.GetRows(sheet)
//	if err != nil {
//		s.err = err
//		return s
//	}
//
//	arrayt := reflect.TypeOf(result)
//	arrayR := reflect.ValueOf(result)
//	arrayv := arrayR
//	if arrayt.Kind() == reflect.Ptr {
//		arrayt = arrayt.Elem()
//		arrayv = arrayv.Elem()
//	}
//
//	if arrayt.Kind() != reflect.Slice {
//		s.err = SliceError
//		return s
//	}
//
//	innerT := arrayt.Elem()
//	if innerT.Kind() == reflect.Ptr {
//		innerT = innerT.Elem()
//	}
//
//	fieldIndex, bindIndex, exMap, count := s.GetIndexMap(innerT)
//
//	arrayv, iSheet, done := s.getHeaderFiled(rows, innerT, fieldIndex, bindIndex, exMap, count, arrayv)
//	if done {
//		return iSheet
//	}
//	arrayR.Elem().Set(arrayv)
//	return s
//}
//
//func (s *sheetTable) getHeaderFiled(rows [][]string, innerT reflect.Type, fieldIndex map[string]int, bindIndex map[string]int, exMap map[string]map[string]string, count int, arrayv reflect.Value) (reflect.Value, ISheet, bool) {
//	headerIndex := make(map[int]string)
//
//	for line, row := range rows {
//		//读取表头
//		if line == 0 {
//			for index, v := range row {
//				headerIndex[index] = strings.TrimSpace(v)
//			}
//			continue
//		}
//
//		tv := reflect.New(innerT)
//		tvElem := tv.Elem()
//
//		findCount, value, sheet, done := s.getElem(row, headerIndex, fieldIndex, bindIndex, exMap, tvElem)
//		if done {
//			return value, sheet, done
//		}
//
//		if count != findCount {
//			s.err = fmt.Errorf("表头异常")
//			return reflect.Value{}, s, true
//		}
//		arrayv = reflect.Append(arrayv, tv)
//	}
//	return arrayv, nil, false
//}
//
//func (s *sheetTable) getElem(row []string, headerIndex map[int]string, fieldIndex map[string]int, bindIndex map[string]int, exMap map[string]map[string]string, tvElem reflect.Value) (int, reflect.Value, ISheet, bool) {
//	var findCount int
//
//	for index, v := range row {
//		v = strings.TrimSpace(v)
//		header := headerIndex[index]
//		fieldNum, ok := fieldIndex[header]
//		if !ok {
//			s.err = fmt.Errorf("表头异常")
//			return 0, reflect.Value{}, s, true
//		}
//
//		findCount += 1
//
//		if v == "" {
//			_, ok = bindIndex[header]
//			if ok {
//				s.err = fmt.Errorf("必须存在的字段为空")
//				return 0, reflect.Value{}, s, true
//			}
//
//			continue
//		}
//
//		exFieldMap, ok := exMap[header]
//		if ok {
//			if exFieldMap[v] == "" {
//				s.err = fmt.Errorf("字段数值不合法，无法解析")
//				return 0, reflect.Value{}, s, true
//			}
//		}
//
//		switch tvElem.Field(fieldNum).Type().String() {
//		case reflect.String.String():
//			tvElem.Field(fieldNum).SetString(v)
//
//		case reflect.Float32.String(), reflect.Float64.String():
//			vf, err := strconv.ParseFloat(v, 64)
//			if err != nil {
//				s.err = err
//				return 0, reflect.Value{}, s, true
//			}
//			tvElem.Field(fieldNum).SetFloat(vf)
//
//		case reflect.Int.String(), reflect.Int64.String(), reflect.Int32.String(), reflect.Int16.String(), reflect.Int8.String():
//			vf, err := strconv.ParseFloat(v, 64)
//			if err != nil {
//				s.err = err
//				return 0, reflect.Value{}, s, true
//			}
//			tvElem.Field(fieldNum).SetInt(int64(vf))
//		}
//	}
//	return findCount, reflect.Value{}, nil, false
//}
//
//func (s *sheetTable) GetIndexMap(innerT reflect.Type) (map[string]int, map[string]int, map[string]map[string]string, int) {
//	fieldIndex := make(map[string]int)
//	bindIndex := make(map[string]int)
//	exMap := make(map[string]map[string]string)
//
//	var count int
//
//	for i := 0; i < innerT.NumField(); i++ {
//		tag := innerT.Field(i).Tag.Get("xlsx")
//		if tag != "" {
//			fieldIndex[tag] = i
//			count += 1
//
//			bindingTag := innerT.Field(i).Tag.Get("binding")
//			if bindingTag == "required" {
//				bindIndex[tag] = i
//			}
//
//			exTag := innerT.Field(i).Tag.Get("ex")
//
//			if exTag != "" {
//				exMap[tag] = map[string]string{}
//
//				for _, v := range strings.Split(exTag, ",") {
//					exMap[tag][v] = v
//				}
//			}
//		}
//	}
//	return fieldIndex, bindIndex, exMap, count
//}

func (x *xlsx) NewSheet(sheet string) ISheet {
	if sheet == DefaultSheet {
		x.isSheet1 = true
	} else {
		x.file.NewSheet(sheet)
	}

	streamWriter, err := x.file.NewStreamWriter(sheet)
	if err != nil {
		return &sheetTable{
			err: err,
		}
	}
	return &sheetTable{
		file:         x.file,
		sheet:        sheet,
		streamWriter: streamWriter,
		row:          2, //默认起始行
	}
}

func Xlsx() IXlsx {
	return &xlsx{}
}

type xlsx struct {
	file     *excelize.File
	isSheet1 bool
}

func (x *xlsx) WriteTo(w io.Writer) (int64, error) {

	return x.file.WriteTo(w)
}

func (x *xlsx) SaveAs(path string) error {
	if !x.isSheet1 {
		x.file.DeleteSheet(DefaultSheet)
	}

	return x.file.SaveAs(path)
}

func (x *xlsx) WriteToBuffer() (*bytes.Buffer, error) {
	if !x.isSheet1 {
		x.file.DeleteSheet(DefaultSheet)
	}
	return x.file.WriteToBuffer()
}

func (x *xlsx) StreamWrite() IXlsx {
	x.file = excelize.NewFile()
	return x
}

func (x *xlsx) Read(reader io.Reader) ISheet {
	file, err := excelize.OpenReader(reader)
	return &sheetTable{
		file: file,
		err:  err,
	}
}

func (x *xlsx) Template(obj interface{}) (*bytes.Buffer, error) {
	x.file = excelize.NewFile()

	t := reflect.TypeOf(obj)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return nil, ObjectError
	}

	header := make([]interface{}, 0)
	defaultV := make([]interface{}, 0)

	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("xlsx")
		if tag != "" {
			header = append(header, tag)
			d := t.Field(i).Tag.Get("default")
			defaultV = append(defaultV, d)
		}

	}

	streamWriter, _ := x.file.NewStreamWriter(DefaultSheet)

	cell, _ := excelize.CoordinatesToCellName(1, 1)
	_ = streamWriter.SetRow(cell, header)

	cell, _ = excelize.CoordinatesToCellName(1, 2)
	_ = streamWriter.SetRow(cell, defaultV)

	_ = streamWriter.Flush()

	return x.file.WriteToBuffer()
}

func objectHeader(obj interface{}) ([]interface{}, error) {
	t := reflect.TypeOf(obj)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return nil, ObjectError
	}

	headerArray := make([]interface{}, 0)
	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("xlsx")
		if tag != "" {
			headerArray = append(headerArray, tag)
		}
	}

	return headerArray, nil
}

func objectValue(obj interface{}) ([]interface{}, error) {
	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		v = v.Elem()
	}

	if t.Kind() != reflect.Struct {
		return nil, ObjectError
	}

	valueArray := make([]interface{}, 0)
	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("xlsx")
		if tag != "" {
			valueArray = append(valueArray, v.Field(i).Interface())
		}
	}

	return valueArray, nil
}

func removeDuplicateElement(languages []string) []string {
	result := make([]string, 0, len(languages))
	temp := map[string]struct{}{}
	for _, item := range languages {
		if _, ok := temp[item]; !ok {
			temp[item] = struct{}{}
			result = append(result, item)
		}
	}
	return result
}
